/**
 * Fingerprint MZ Module
 *
 * This module provides an interface for communicating with the MZ fingerprint module via UART.
 * It supports operations like image capture, fingerprint registration, matching, and storage management.
 *
 * Features:
 * - `Image Capture`: Capture fingerprint images for enrollment or verification.
 * - `Registration`: Generate and store fingerprint templates.
 * - `Matching`: One-to-one (1:1) and one-to-many (1:N) fingerprint matching.
 * - `Storage`: Manage fingerprint templates in the module's flash storage.
 * - `System`: Configure module parameters and read system status.
 *
 * Usage:
 * import dxFingerMz from './dxFingerMz.js';
 * dxFingerMz.init({ id: 'fingerUart', path: '/dev/ttySLB0' });
 * const result = dxFingerMz.getImage();
 */
import std from '../dxmodules/dxStd.js'
import dxUart from '../dxmodules/dxUart.js'
import common from '../dxmodules/dxCommon.js'
import * as os from "os"

const dxFingerMz = {}

// Packet Header
const HEADER = new Uint8Array([0xEF, 0x01])
// Device Address
let ADDRESS = new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF])

// Packet Flags
const FLAG = {
    "COMMAND": 0x01,     // Command packet
    "DATA": 0x02,        // Data packet
    "DATA_END": 0x08,    // End of data packet
    "COMMAND_END": 0x07, // Response packet
}

// Command Set
const CMD_MAP = {
    /* General Commands */
    "GET_IMAGE": 0x01,          // Get image for verification
    "GET_CHAR": 0x02,           // Generate character file from image
    "MATCH": 0x03,              // Precise match
    "SEARCH": 0x04,             // Search fingerprint
    "REG_MODEL": 0x05,          // Combine character files (generate template)
    "STORD_CHAR": 0x06,         // Store template
    "LOAD_CHAR": 0x07,          // Load template
    "UP_CHAR": 0x08,            // Upload template
    "DOWN_CHAR": 0x09,          // Download template
    "DEL_CHAR": 0x0C,           // Delete template
    "CLEAR_CHAR": 0x0D,         // Clear fingerprint library
    "WRITE_REG": 0x0E,          // Write system register
    "READ_SYS_PARA": 0x0F,      // Read system parameters
    "READ_INF_PAGE": 0x16,      // Read information page
    "BURN_CODE": 0x1a,          // Burn code (Erase code)
    "VALID_TEMPLETE_NUM": 0x1d, // Read valid template count
    "READ_INDEX_TABLE": 0x1f,   // Read index table
    "GET_ENROLL_IMAGE": 0x29,   // Get image for enrollment
    "SLEEP": 0x33,              // Sleep command

    /* Module Commands */
    "CANCEL": 0x30,             // Cancel command
    "AUTO_ENROLL": 0x31,        // Auto enroll template
    "AUTO_IDENTIFY": 0x32,      // Auto identify fingerprint

    /* Maintenance Commands */
    "UP_IMAGE": 0x0A,           // Upload image
    "DOWN_IMAGE": 0x0B,         // Download image
    "GET_CHIP_SN": 0x34,        // Get chip serial number
    "HAND_SHAKE": 0x35,         // Handshake
    "CHECK_SENSOR": 0x36,       // Check sensor
    "REST_SETTING": 0x3B,       // Restore factory settings

    /* Custom Commands */
    "SET_PWD": 0x12,            // Set password
    "VFY_PWD": 0x13,            // Verify password
    "GET_RANDOM_CODE": 0x14,    // Get random code
    "SET_CHIP_ADDR": 0x15,      // Set chip address
    "WRITE_NOTEPAD": 0x18,      // Write notepad
    "READ_NOTEPAD": 0x19,       // Read notepad
    "CONTROL_BLN": 0x3C,        // Control LED light
    "GET_IMAGE_INFO": 0x3D,     // Get image info
    "SEARCH_NOW": 0x3E,         // Search current fingerprint
}

let options = {
    id: 'fingerUart',
    total: 5000,
    timeout: 500,
    type: '3',
    path: '/dev/ttySLB0',
    baudrate: '57600-8-N-2'
}

/**
 * Get Image (Verification) - Detects finger and stores the fingerprint image in the image buffer.
 * @returns {number} Confirmation code (0 for success, others for failure).
 */
dxFingerMz.getImage = function () {
    let resp = execCmd(CMD_MAP.GET_IMAGE)
    if (resp && resp.flag[0] == FLAG.COMMAND_END) {
        return resp.data
    } else {
        return -1
    }
}

/**
 * Generate Character File - Generates a fingerprint character file from the image in the image buffer and stores it in the character buffer.
 * @param {number} bufferId - Buffer ID (1 or 2).
 * @returns {number} Confirmation code (0 for success, others for failure).
 */
dxFingerMz.genChar = function (bufferId) {
    let resp = execCmd(CMD_MAP.GET_CHAR, [bufferId])
    if (resp && resp.flag[0] == FLAG.COMMAND_END) {
        return resp.data
    } else {
        return -1
    }
}

/**
 * Precise Match - Compares the character file or template in the buffer.
 * @returns {object|null} An object containing the result code and match score, or null on failure.
 *                        - code: Confirmation code.
 *                        - score: Match score.
 */
dxFingerMz.match = function () {
    let resp = execCmd(CMD_MAP.MATCH)
    if (resp && resp.flag[0] == FLAG.COMMAND_END) {
        return { code: resp.data[0], score: fromEndianHexExtended(resp.data.subarray(1, 3), 2, false) }
    } else {
        return null
    }
}

/**
 * Search Fingerprint - Searches the fingerprint library (whole or partial) using the character file in the buffer.
 * If found, returns the page number.
 * @param {number} bufferId - Character buffer ID (default is 1).
 * @param {number} startPage - Start page number.
 * @param {number} pageNum - Number of pages to search.
 * @returns {object|null} An object containing the result code, page index, and score, or null on failure.
 *                        - code: Confirmation code.
 *                        - pageIndex: Index of the found page.
 *                        - score: Match score.
 */
dxFingerMz.search = function (bufferId, startPage, pageNum) {
    let data = new Uint8Array([bufferId, ...toEndianHexExtended(startPage, 2, false), ...toEndianHexExtended(pageNum, 2, false)])
    let resp = execCmd(CMD_MAP.SEARCH, data)
    if (resp && resp.flag[0] == FLAG.COMMAND_END) {
        return { code: resp.data[0], pageIndex: fromEndianHexExtended(resp.data.subarray(1, 3), 2, false), score: fromEndianHexExtended(resp.data.subarray(3, 5), 2, false) }
    } else {
        return null
    }
}

/**
 * Register Model (Combine Characters) - Combines character files to generate a template and stores it in the character buffer.
 * @returns {number} Confirmation code (0 for success, others for failure).
 */
dxFingerMz.regModel = function () {
    let resp = execCmd(CMD_MAP.REG_MODEL)
    if (resp && resp.flag[0] == FLAG.COMMAND_END) {
        return resp.data
    } else {
        return -1
    }
}

/**
 * Store Template - Stores the template file from the buffer to the flash database at the specified page index.
 * @param {number} bufferId - Character buffer ID (default is 1).
 * @param {number} pageIndex - Fingerprint library location index.
 * @returns {number} Confirmation code (0 for success, others for failure).
 */
dxFingerMz.storeChar = function (bufferId, pageIndex) {
    let data = new Uint8Array([bufferId, ...toEndianHexExtended(pageIndex, 2, false)])
    let resp = execCmd(CMD_MAP.STORD_CHAR, data)
    if (resp && resp.flag[0] == FLAG.COMMAND_END) {
        return resp.data
    } else {
        return -1
    }
}

/**
 * Load Template - Reads the fingerprint template from the flash database at the specified page index into the buffer.
 * @param {number} bufferId - Character buffer ID (default is 2).
 * @param {number} pageIndex - Fingerprint library location index.
 * @returns {number} Confirmation code (0 for success, others for failure).
 */
dxFingerMz.loadChar = function (bufferId, pageIndex) {
    let data = new Uint8Array([bufferId, ...toEndianHexExtended(pageIndex, 2, false)])
    let resp = execCmd(CMD_MAP.LOAD_CHAR, data)
    if (resp && resp.flag[0] == FLAG.COMMAND_END) {
        return resp.data
    } else {
        return -1
    }
}

/**
 * Upload Template - Uploads the template file from the buffer to the Host.
 * @param {number} bufferId - Character buffer ID (default is 2).
 * @returns {ArrayBuffer|null} Template data, or null on failure.
 */
dxFingerMz.upChar = function (bufferId) {
    let resp = execCmd(CMD_MAP.UP_CHAR, [bufferId])
    if (resp && resp.flag[0] == FLAG.COMMAND_END) {
        let data = recvDataPkt({ header: HEADER, addr: ADDRESS, flag: FLAG.DATA })
        return data
    } else {
        return null
    }
}

/**
 * Download Template - Downloads a template from the Host to the module's buffer.
 * @param {number} bufferId - Character buffer ID (default is 1).
 * @param {ArrayBuffer} char - Template data.
 * @returns {number} Confirmation code (0 for success, others for failure).
 */
dxFingerMz.downChar = function (bufferId, char) {
    let resp = execCmd(CMD_MAP.DOWN_CHAR, [bufferId])
    if (resp && resp.flag[0] == FLAG.COMMAND_END) {
        sendData({
            header: HEADER, addr: ADDRESS, flag: new Uint8Array([FLAG.DATA]), len: new Uint8Array(toEndianHexExtended(2, 2, false)), cmd: new Uint8Array([CMD_MAP.DOWN_CHAR]),
            data: char
        })
        return resp.data
    } else {
        return -1
    }
}

/**
 * Delete Template - Deletes N fingerprint templates starting from the specified page index in the flash database.
 * @param {number} pageIndex - Starting index in the fingerprint library.
 * @param {number} num - Number of templates to delete.
 * @returns {number} Confirmation code (0 for success, others for failure).
 */
dxFingerMz.deletChar = function (pageIndex, num) {
    let data = new Uint8Array([...toEndianHexExtended(pageIndex, 2, false), ...toEndianHexExtended(num, 2, false)])
    let resp = execCmd(CMD_MAP.DEL_CHAR, data)
    if (resp && resp.flag[0] == FLAG.COMMAND_END) {
        return resp.data
    } else {
        return -1
    }
}

/**
 * Clear Library (PS_Empty) - Deletes all fingerprint templates in the flash database.
 * @returns {number} Confirmation code (0 for success, others for failure).
 */
dxFingerMz.clearChar = function () {
    let resp = execCmd(CMD_MAP.CLEAR_CHAR)
    if (resp && resp.flag[0] == FLAG.COMMAND_END) {
        return resp.data
    } else {
        return -1
    }
}

/**
 * Write System Register - Writes to a module register.
 * @param {number} regId - Register ID.
 * @param {number} context - Content to write.
 * @returns {number} Confirmation code (0 for success, others for failure).
 */
dxFingerMz.writeReg = function (regId, context) {
    let resp = execCmd(CMD_MAP.WRITE_REG, [regId, context])
    if (resp && resp.flag[0] == FLAG.COMMAND_END) {
        return resp.data
    } else {
        return -1
    }
}

/**
 * Read System Parameters - Reads the module's basic parameters (baud rate, packet size, etc.).
 * @returns {object|null} An object containing the result code and parameter data, or null on failure.
 *                        - code: Confirmation code.
 *                        - data: System parameters.
 */
dxFingerMz.readSysPara = function () {
    let resp = execCmd(CMD_MAP.READ_SYS_PARA)
    if (resp && resp.flag[0] == FLAG.COMMAND_END) {
        return { code: resp.data[resp.data.length - 1], data: resp.data.subarray(0, resp.data.length - 1) }
    } else {
        return null
    }
}

/**
 * Get Valid Template Count - Reads the number of valid templates.
 * @returns {object|null} An object containing the result code and valid count, or null on failure.
 *                        - code: Confirmation code.
 *                        - validNum: Number of valid templates.
 */
dxFingerMz.getValidTemplateNum = function () {
    let resp = execCmd(CMD_MAP.VALID_TEMPLETE_NUM)
    if (resp && resp.flag[0] == FLAG.COMMAND_END) {
        return { code: resp.data[0], validNum: resp.data[1] }
    } else {
        return null
    }
}

/**
 * Read Index Table - Reads the index table of enrolled templates.
 * @param {number} indexPage - Index table page number (0, 1, 2, 3...). Each page corresponds to a range of templates (0-255, 256-511, etc.).
 *                             Each bit represents a template: 1 means enrolled, 0 means not enrolled.
 * @returns {object|null} An object containing the result code and index table, or null on failure.
 *                        - code: Confirmation code.
 *                        - indexTable: The index table data.
 */
dxFingerMz.readIndexTable = function (indexPage) {
    let resp = execCmd(CMD_MAP.READ_INDEX_TABLE, [indexPage])
    if (resp && resp.flag[0] == FLAG.COMMAND_END) {
        return { code: resp.data[0], indexTable: resp.data.subarray(1) }
    } else {
        return null
    }
}

/**
 * Get Enroll Image - Detects finger and stores the fingerprint image in the buffer (for registration).
 * @returns {number} Confirmation code (0 for success, others for failure).
 */
dxFingerMz.getEnrollImage = function () {
    let resp = execCmd(CMD_MAP.GET_ENROLL_IMAGE)
    if (resp && resp.flag[0] == FLAG.COMMAND_END) {
        return resp.data
    } else {
        return -1
    }
}

/**
 * Sleep - Sets the sensor to sleep mode.
 * @returns {number} Confirmation code (0 for success, others for failure).
 */
dxFingerMz.sleep = function () {
    let resp = execCmd(CMD_MAP.SLEEP)
    if (resp && resp.flag[0] == FLAG.COMMAND_END) {
        return resp.data
    } else {
        return -1
    }
}

/**
 * Cancel - Cancels auto-registration or auto-verification operations.
 * @returns {number} Confirmation code (0 for success, others for failure).
 */
dxFingerMz.cancel = function () {
    let resp = execCmd(CMD_MAP.CANCEL)
    if (resp && resp.flag[0] == FLAG.COMMAND_END) {
        return resp.data
    } else {
        return -1
    }
}

/**
 * Auto Register - One-stop fingerprint registration. Includes capture, feature generation, template merging, and storage.
 * @param {number} pageIndex - Page index.
 * @param {number} count - Number of finger presses required.
 * @param {number} [timeout=60] - Timeout in seconds.
 * @param {number} [config=0] - Configuration flags.
 * @returns {ArrayBuffer|null} Result data, or null on failure/timeout.
 */
dxFingerMz.autoRegister = function (pageIndex, count, timeout = 60, config = 0) {
    let param = {
        header: HEADER, addr: ADDRESS, flag: new Uint8Array([FLAG.COMMAND]), len: new Uint8Array(toEndianHexExtended(8, 2, false)), cmd: new Uint8Array([CMD_MAP.AUTO_ENROLL])
        , data: new Uint8Array([...toEndianHexExtended(pageIndex, 2, false), count, ...toEndianHexExtended(config, 2, false)])
    }
    // Send command
    send(param)
    // For each press, there will be multiple responses (status, merge result, duplicate check, store result).
    let startTime = Date.now()
    while (Date.now() - startTime < timeout * 1000) {
        let resp = recvCmdPkt({ addr: ADDRESS })
        if (resp && resp.flag[0] == FLAG.COMMAND_END && resp.data[0] == 0x00 && resp.data[1] == 0x06 && resp.data[2] == 0xf2) {
            return resp.data
        }
        os.sleep(50)
    }
    return null
}

/**
 * Auto Compare - Automatic fingerprint verification. Includes image capture, feature generation, and search.
 * @param {number} pageIndex - Template index for 1:1 match, or 0xFFFF for 1:N search.
 * @param {number} scoreLevel - Security level (1-5, default is 3).
 * @param {number} config - Configuration flags.
 * @returns {object|null} An object containing the result code, index, and score, or null on failure.
 *                        - code: Confirmation code.
 *                        - index: Matched index.
 *                        - score: Match score.
 */
dxFingerMz.autoCompare = function (pageIndex, scoreLevel, config) {
    let param = {
        header: HEADER, addr: ADDRESS, flag: new Uint8Array([FLAG.COMMAND]), len: new Uint8Array(toEndianHexExtended(8, 2, false)), cmd: new Uint8Array([CMD_MAP.AUTO_IDENTIFY])
        , data: new Uint8Array([scoreLevel, ...toEndianHexExtended(pageIndex, 2, false), ...toEndianHexExtended(config, 2, false)])
    }
    // Send command
    send(param)
    // Expect 3 responses: 1. Command validity, 2. Image result, 3. Search result.
    for (let i = 0; i < 3; i++) {
        let resp = recvCmdPkt({ addr: ADDRESS })
        if (resp && resp.flag[0] == FLAG.COMMAND_END && resp.data[0] == 0 && resp.data[1] == 0x05) {
            return { code: resp.data[0], index: fromEndianHexExtended(resp.data.subarray(2, 4), 2, false), score: fromEndianHexExtended(resp.data.subarray(4, 6), 2, false) }
        }
    }
    return null
}

/**
 * Get Chip SN - Retrieves the unique serial number of the chip.
 * @returns {object|null} An object containing the result code and serial number, or null on failure.
 *                        - code: Confirmation code.
 *                        - sn: Serial number string/buffer.
 */
dxFingerMz.getChipSN = function () {
    let resp = execCmd(CMD_MAP.GET_CHIP_SN, [0x00])
    if (resp && resp.flag[0] == FLAG.COMMAND_END) {
        return { code: resp.data[0], sn: resp.data.subarray(1) }
    } else {
        return null
    }
}

/**
 * Search Now - Searches the library using the most recently extracted feature in the character buffer.
 * @param {number} startPage - Start page index.
 * @param {number} pageNum - Number of pages to search.
 * @returns {object|null} An object containing the result code, page index, and score, or null on failure.
 *                        - code: Confirmation code.
 *                        - pageIndex: Found page index.
 *                        - score: Match score.
 */
dxFingerMz.searchNow = function (startPage, pageNum) {
    let data = new Uint8Array([...toEndianHexExtended(startPage, 2, false), ...toEndianHexExtended(pageNum, 2, false)])
    let resp = execCmd(CMD_MAP.SEARCH_NOW, data)
    if (resp && resp.flag[0] == FLAG.COMMAND_END) {
        return { code: resp.data[0], pageIndex: fromEndianHexExtended(resp.data.subarray(1, 3), 2, false), score: fromEndianHexExtended(resp.data.subarray(3, 5), 2, false) }
    } else {
        return null
    }
}

/**
 * Restore Factory Settings - Clears internal data (if enrolled) and deletes internal keys.
 * @returns {number} Confirmation code (0 for success, others for failure).
 */
dxFingerMz.restSetting = function () {
    let resp = execCmd(CMD_MAP.REST_SETTING)
    if (resp && resp.flag[0] == FLAG.COMMAND_END) {
        return resp.data
    } else {
        return -1
    }
}

/**
 * Retrieve the first unused index from the fingerprint database
 * @returns {number} Return the first unused index, returning -1 indicates retrieval failure
 */
dxFingerMz.getIndex = function () {
    // The fingerprint database has four page numbers, 0-3. Traverse and search these four page numbers
    for(let i = 0; i < 4; i++){
        let inx = dxFingerMz.readIndexTable(i)
        if(inx && inx.code == 0){
            let ret = findFreeFingerIndex(inx.indexTable, i)
            if(ret != -1){
                return ret
            }
        }
    }
    return -1
}

/**
 * Set Device Address - Sets the device address (default is 0xFFFFFFFF).
 * @param {number} startId - Start ID.
 * @param {number} endId - End ID.
 * @returns {number} The available fingerprint ID, or false on failure.
 */
dxFingerMz.setChipAddr = function (startId, endId) {
}

/**
 * Initialize Fingerprint Module.
 * @param {Object} params - Initialization parameters.
 * @param {string} [params.type='3'] - UART type.
 * @param {string} [params.path='/dev/ttySLB1'] - UART path.
 * @param {string} [params.baudrate='115200-8-N-1'] - Baud rate configuration.
 * @param {string} [params.id='fingerUart'] - Connection ID.
 * @param {number} [params.total=5000] - Total fingerprint capacity.
 * @param {number} [params.timeout=500] - Timeout in milliseconds.
 */
dxFingerMz.init = function (params) {
    options.id = params.id ? params.id : options.id
    options.timeout = params.timeout ? params.timeout : options.timeout
    options.total = params.total ? params.total : options.total
    options.type = params.type ? params.type : options.type
    options.path = params.path ? params.path : options.path
    options.baudrate = params.baudrate ? params.baudrate : options.baudrate

    dxUart.open(options.type, options.path, options.id)
    dxUart.ioctl(6, options.baudrate, options.id)
}

/** *****************************private******************************* */

/**
 * Execute Command - Sends a command and receives the response.
 * @private
 * @param {number} cmd - The command to execute.
 * @param {Uint8Array|Array} [data] - Optional data payload.
 * @returns {Object|null} The response packet, or null on failure.
 */
function execCmd(cmd, data) {
    let len = 3 // cmd(1) + checksum(2)
    let cmdData = data
    if (data) {
        if (Array.isArray(data)) {
            cmdData = new Uint8Array(data)
        }
        len += cmdData.length
    }

    let param = {
        header: HEADER,
        addr: ADDRESS,
        flag: new Uint8Array([FLAG.COMMAND]),
        len: new Uint8Array(toEndianHexExtended(len, 2, false)),
        cmd: new Uint8Array([cmd])
    }

    if (cmdData) {
        param.data = cmdData
    }

    // Send command
    send(param)
    return recvCmdPkt({ addr: ADDRESS })
}

/**
 * Send Packet
 * @private
 * @param {Object} params - Packet parameters.
 * @param {number} params.header - Packet header.
 * @param {number} params.addr - Device address.
 * @param {number} params.flag - Packet flag.
 * @param {number} params.len - Packet length.
 * @param {number} params.cmd - Command byte (optional).
 * @param {string} params.data - Data payload (optional).
 */
function send(params) {
    if (!params || typeof params !== 'object') {
        throw new Error("Parameters should be an object containing prefix, cmd and data");
    }

    const { header, addr, flag, len, cmd, data } = params;
    // Build packet
    let packet = concatUint8Arrays(header, addr, flag, len, cmd, data, new Uint8Array([0x00, 0x00]));

    // Calculate checksum
    let checksum = 0;
    for (let i = 6; i < packet.length; i++) {
        checksum += packet[i];
    }
    checksum = Math.min(checksum, 0xFFFF);
    // Set checksum
    packet.set(new Uint8Array(toEndianHexExtended(checksum, 2, false)), packet.length - 2);

    // Send packet via UART
    let ret = dxUart.send(packet.buffer, options.id);
    if (!ret) {
        throw new Error("dxFingerMz.send fail")
    }
}

/**
 * Send Data Packet - Splits data into chunks and sends them.
 * @private
 * @param {Object} params - Parameters.
 * @param {string|Uint8Array} params.data - Data to send.
 */
function sendData(param) {
    let dataLen = param.data.length
    let transNum = Math.ceil(dataLen / 128)
    for (let i = 0; i < transNum; i++) {
        let data = param.data.subarray(i * 128, (i + 1) * 128 > dataLen ? dataLen : (i + 1) * 128)
        let childParam = {
            header: HEADER, addr: ADDRESS, flag: new Uint8Array([FLAG.DATA]), len: new Uint8Array(toEndianHexExtended(2, 2, false))
            , data: data
        }
        childParam.len = new Uint8Array(toEndianHexExtended(data.length + 2, 2, false))
        if (i == transNum - 1) {
            childParam.flag = new Uint8Array([FLAG.DATA_END])
        } else {
            childParam.flag = new Uint8Array([FLAG.DATA])
        }
        send(childParam)
        os.sleep(100)
    }
}

/**
 * Receive Packet - Reads a single packet from UART.
 * @private
 * @param {Object} param - Parameters.
 * @param {Uint8Array} param.addr - Expected address.
 * @returns {Object|null} Received packet object, or null if invalid/timeout.
 */
function receive(param) {
    const header1 = dxUart.receive(1, options.timeout, options.id);
    if (!header1 || header1[0] !== 0xEF) {
        std.sleep(50)
        return null
    }
    const header2 = dxUart.receive(1, options.timeout, options.id);
    if (!header2 || header2[0] !== 0x01) {
        std.sleep(50)
        return null
    }

    // Receive addr
    const addr = dxUart.receive(4, options.timeout, options.id);
    if (common.arrayBufferToHexString(addr) != common.arrayBufferToHexString(param.addr)) {
        return null
    }

    // Receive flag
    const flag = dxUart.receive(1, options.timeout, options.id);

    // Receive len
    const len = dxUart.receive(2, options.timeout, options.id);

    // Receive data
    const data = dxUart.receive((fromEndianHexExtended(len, 2, false)) - 2, options.timeout, options.id);

    // Receive checksum
    const checksumPkt = dxUart.receive(2, options.timeout, options.id);

    // Verify checksum
    let checksum = 0;
    let packet = concatUint8Arrays(flag, len, data);
    for (let i = 0; i < packet.length; i++) {
        checksum += packet[i];
    }
    checksum = Math.min(checksum, 0xFFFF);
    if (checksum != fromEndianHexExtended(checksumPkt, 2, false)) {
        return null
    }

    let allPack = concatUint8Arrays(new Uint8Array([header1, header2]), addr, flag, len, data, checksumPkt);

    return {
        header: new Uint8Array([header1, header2]),
        addr: addr,
        flag: flag,
        len: len,
        data: data,
        checksum: checksum
    };
}

/**
 * Receive Data Packet - Receives a multi-packet data stream.
 * @private
 * @param {Object} param - Parameters.
 * @returns {Uint8Array} The complete data buffer.
 */
function recvDataPkt(param) {
    let chunks = [];  // Collect all chunks
    let totalLength = 0;
    let deadlineTime = Date.now() + 2000;

    while (Date.now() < deadlineTime) {
        const data = receive(param);
        if (data) {
            chunks.push(data.data);
            totalLength += data.data.length;
        } else {
            break;
        }
        os.sleep(10);
    }

    // Merge all chunks
    const packet = new Uint8Array(totalLength);
    let offset = 0;
    for (const chunk of chunks) {
        packet.set(chunk, offset);
        offset += chunk.length;
    }

    return packet;
}

/**
 * Receive Command Packet - Attempts to receive a command response multiple times.
 * @private
 * @param {Object} param - Parameters.
 * @returns {Object|null} Response packet, or null on failure.
 */
function recvCmdPkt(param) {
    for (let i = 0; i < 5; i++) {
        const packet = receive(param);
        if (packet) {
            return packet
        }
        os.sleep(10)
    }
    return null
}

/**
 * Convert number to little-endian (default) hex byte array.
 * @private
 * @param {number} number - The number to convert.
 * @param {number} bytes - Number of bytes.
 * @param {boolean} isLittleEndian - Little endian flag.
 * @returns {Uint8Array} The byte array.
 */
function toEndianHexExtended(number, bytes, isLittleEndian = true) {
    const buffer = new Uint8Array(bytes);
    for (let i = 0; i < bytes; i++) {
        // Bitwise operation, taking 8 bits at a time
        buffer[i] = (number >> (i * 8)) & 0xFF;
    }
    // If big endian, reverse the array
    if (!isLittleEndian) {
        buffer.reverse();
    }
    return buffer;
}

/**
 * Convert byte array to number (supports little/big endian).
 * @private
 * @param {Uint8Array|number[]} bytesArray - The byte array.
 * @param {number} bytes - Number of bytes.
 * @param {boolean} isLittleEndian - Little endian flag.
 * @returns {number} The converted number.
 */
function fromEndianHexExtended(bytesArray, bytes, isLittleEndian = true) {
    // Parameter validation
    if (!bytesArray || bytesArray.length !== bytes) {
        throw new Error(`Invalid bytes array. Expected length: ${bytes}`);
    }

    let result = 0;
    if (isLittleEndian) {
        // Little endian: least significant byte first
        for (let i = 0; i < bytes; i++) {
            result |= (bytesArray[i] << (i * 8));
        }
    } else {
        // Big endian: most significant byte first
        for (let i = 0; i < bytes; i++) {
            result = (result << 8) | bytesArray[i];
        }
    }

    // Return as unsigned integer
    return result >>> 0;
}

/**
 * Concatenate multiple Uint8Arrays.
 * @private
 * @param {...Uint8Array} arrays - Arrays to concatenate.
 * @returns {Uint8Array} The concatenated array.
 */
function concatUint8Arrays(...arrays) {
    const validArrays = arrays.filter(arr => arr && arr.length > 0);
    const totalLength = validArrays.reduce((acc, arr) => acc + arr.length, 0);

    const result = new Uint8Array(totalLength);
    let offset = 0;
    validArrays.forEach(arr => {
        result.set(arr, offset);
        offset += arr.length;
    });
    return result
}

/**
 * Find the earliest (smallest) unused template index in the fingerprint module
 * 
 * @param {Uint8Array} buf The element is a 32-byte Uint8Array
 * @returns {number} Return the available template number (0~1023). Returning -1 indicates no empty space.
 */
function findFreeFingerIndex(buf, page) {

    // Traverse 32 bytes → 256 bits
    for (let byteIndex = 0; byteIndex < 32; byteIndex++) {
        let byte = buf[byteIndex];

        // byte == 0xFF means all 8 bits are occupied, skip
        if (byte === 0xFF) continue;

        // Find which bit in byte is 0
        for (let bit = 0; bit < 8; bit++) {
            let used = (byte >> bit) & 1;

            if (used === 0) {
                // Global index = page offset + bit position
                let globalIndex = page * 256 + byteIndex * 8 + bit;
                return globalIndex;
            }
        }
    }

    return -1; // No empty space
}

export default dxFingerMz;
